#!/usr/bin/env python
# -*- coding: utf-8 -*-

import requests
import json
import os
import re
import time
import random
import youtube_dl
import base64
import hashlib
from urllib.parse import quote, urlencode
from mutagen.id3 import ID3, APIC, TIT2, TPE1, TALB
from mutagen.flac import FLAC, Picture

class MusicAPI:
    """音乐搜索和下载API接口"""
    
    def __init__(self):
        # API相关配置
        self.search_headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36',
            'Referer': 'https://music.163.com/',
            'Origin': 'https://music.163.com'
        }
        
        self.download_headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36',
            'Accept': '*/*',
            'Accept-Encoding': 'gzip, deflate, br',
            'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
            'Range': 'bytes=0-'
        }
        
        # 网易云音乐官方API地址
        self.netease_search_url = "https://music.163.com/api/search/get"
        self.netease_song_url = "http://music.163.com/song/media/outer/url?id="
        
        # QQ音乐API
        self.qq_search_url = "https://u.y.qq.com/cgi-bin/musicu.fcg"
        self.qq_song_url = "https://u.y.qq.com/cgi-bin/musicu.fcg"
    
    def search_music(self, keyword, limit=10, source="netease"):
        """
        搜索音乐，支持多个音乐源
        
        参数:
            keyword (str): 搜索关键词
            limit (int): 返回结果数量限制
            source (str): 音乐源，支持 "netease"(网易云音乐) 和 "qq"(QQ音乐)
            
        返回:
            list: 搜索结果列表
        """
        if source == "netease":
            return self._search_netease(keyword, limit)
        elif source == "qq":
            return self._search_qq(keyword, limit)
        else:
            # 如果源不支持，返回模拟数据作为备选
            return self._search_mock(keyword, limit)
    
    def _search_netease(self, keyword, limit=10):
        """从网易云音乐搜索 - 直接使用官方API"""
        max_retries = 3
        retry_count = 0
        
        while retry_count < max_retries:
            try:
                # 构建搜索参数
                params = {
                    's': keyword,
                    'type': 1,  # 1: 单曲, 10: 专辑, 100: 歌手, 1000: 歌单, 1002: 用户, 1004: MV, 1006: 歌词, 1009: 电台, 1014: 视频
                    'offset': 0,
                    'limit': limit,
                    'total': 'true'
                }
                
                # 构建请求头
                headers = {
                    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36',
                    'Referer': 'https://music.163.com/',
                    'Content-Type': 'application/x-www-form-urlencoded'
                }
                
                # 发送请求
                response = requests.post(self.netease_search_url, params=params, headers=headers, timeout=10)
                
                # 检查状态码
                if response.status_code != 200:
                    print(f"网易云搜索API请求失败: 状态码 {response.status_code}")
                    retry_count += 1
                    if retry_count < max_retries:
                        print(f"尝试重试 ({retry_count}/{max_retries})...")
                        time.sleep(1)  # 等待1秒后重试
                        continue
                    return self._search_mock(keyword, limit)
                
                # 解析响应
                data = response.json()
                
                # 检查响应是否有效
                if not data.get('result') or not data['result'].get('songs'):
                    # 如果搜索结果为空，返回模拟数据
                    print(f"网易云搜索API返回无结果")
                    return self._search_mock(keyword, limit)
                
                # 获取歌曲列表
                songs = data['result']['songs']
                result_list = []
                
                # 解析歌曲数据
                for song in songs:
                    # 获取基本信息
                    song_id = str(song.get('id', ''))
                    song_name = song.get('name', '未知歌曲')
                    
                    # 获取专辑信息
                    album_name = '未知专辑'
                    pic_url = ''
                    album_id = ''
                    if 'album' in song:
                        album_name = song['album'].get('name', '未知专辑')
                        album_id = song['album'].get('id', '')
                        if 'picUrl' in song['album'] and song['album']['picUrl']:
                            # 修复封面图片URL，确保使用https
                            pic_url = song['album']['picUrl'].replace('http://', 'https://')
                            # 添加图片参数，获取更清晰的图片
                            if '?' not in pic_url:
                                pic_url += '?param=300y300'
                    
                    # 获取歌手信息
                    artists = []
                    if 'artists' in song and song['artists']:
                        artists = [artist.get('name', '') for artist in song['artists'] if artist.get('name')]
                    artist_name = '/'.join(artists) if artists else '未知歌手'
                    
                    # 获取时长
                    duration_ms = song.get('duration', 0)
                    minutes, seconds = divmod(duration_ms // 1000, 60)
                    duration = f"{minutes}:{seconds:02d}"
                    
                    # 获取歌曲是否为VIP
                    is_vip = song.get('fee', 0) == 1
                    
                    # 估算文件大小
                    # 假设比特率：MP3 320kbps，FLAC 900kbps
                    mp3_size = round(duration_ms / 1000 * 320 / 8 / 1024, 1)  # MB
                    flac_size = round(duration_ms / 1000 * 900 / 8 / 1024, 1)  # MB
                    
                    # 为VIP歌曲添加备注
                    size_mp3_text = f"{mp3_size} MB"
                    size_flac_text = f"{flac_size} MB"
                    if is_vip:
                        size_mp3_text += " (VIP)"
                        size_flac_text += " (VIP)"
                    
                    # 创建结果对象
                    result_list.append({
                        'id': song_id,
                        'name': song_name,
                        'artist': artist_name,
                        'album': album_name,
                        'album_id': album_id,
                        'duration': duration,
                        'size_mp3': size_mp3_text,
                        'size_flac': size_flac_text,
                        'url_mp3': f"{self.netease_song_url}{song_id}.mp3",
                        'url_flac': f"{self.netease_song_url}{song_id}.mp3",  # 直接使用MP3链接，网易云官方API不提供FLAC链接
                        'pic_url': pic_url,
                        'source': 'netease',
                        'is_vip': is_vip
                    })
                
                return result_list
                
            except requests.exceptions.Timeout:
                print(f"网易云搜索请求超时")
                retry_count += 1
                if retry_count < max_retries:
                    print(f"尝试重试 ({retry_count}/{max_retries})...")
                    time.sleep(1)  # 等待1秒后重试
                    continue
                return self._search_mock(keyword, limit)
                
            except requests.exceptions.ConnectionError:
                print(f"网易云搜索连接错误")
                retry_count += 1
                if retry_count < max_retries:
                    print(f"尝试重试 ({retry_count}/{max_retries})...")
                    time.sleep(2)  # 网络错误等待时间更长
                    continue
                return self._search_mock(keyword, limit)
                
            except Exception as e:
                print(f"网易云搜索请求出错: {str(e)}")
                retry_count += 1
                if retry_count < max_retries:
                    print(f"尝试重试 ({retry_count}/{max_retries})...")
                    time.sleep(1)  # 等待1秒后重试
                    continue
                return self._search_mock(keyword, limit)
                
        return self._search_mock(keyword, limit)
    
    def _search_qq(self, keyword, limit=10):
        """从QQ音乐搜索"""
        try:
            # 随机生成callback和guid
            callback = f"MusicJsonCallback{int(time.time() * 1000)}"
            guid = ''.join(random.sample('1234567890', 10))
            
            # 构建请求参数
            req_data = {
                'req': {
                    'module': 'music.search.SearchCgiService',
                    'method': 'DoSearchForQQMusicDesktop',
                    'param': {
                        'query': keyword,
                        'page_num': 1,
                        'num_per_page': limit
                    }
                }
            }
            
            params = {
                'callback': callback,
                'g_tk': '5381',
                'jsonpCallback': callback,
                'loginUin': '0',
                'hostUin': '0',
                'format': 'jsonp',
                'inCharset': 'utf8',
                'outCharset': 'utf-8',
                'notice': '0',
                'platform': 'yqq',
                'needNewCode': '0',
                'data': json.dumps(req_data)
            }
            
            # 发送请求
            try:
                url = f"{self.qq_search_url}?{urlencode(params)}"
                response = requests.get(url, headers=self.search_headers, timeout=10)
                
                if response.status_code != 200:
                    return self._search_mock(keyword, limit)
                
                # 处理jsonp返回值，提取json部分
                match = re.search(r'^\w+\((.*)\)$', response.text)
                if not match:
                    return self._search_mock(keyword, limit)
                
                json_str = match.group(1)
                data = json.loads(json_str)
                
                # 解析响应
                if 'req' not in data or 'data' not in data['req'] or 'body' not in data['req']['data'] or 'song' not in data['req']['data']['body']:
                    return self._search_mock(keyword, limit)
                
                songs = data['req']['data']['body']['song']['list']
                result_list = []
                
                for song in songs:
                    # 获取歌曲信息
                    song_id = song.get('mid', '')
                    song_name = song.get('name', '')
                    album_name = song.get('album', {}).get('name', '未知专辑')
                    
                    # 获取歌手信息
                    singers = song.get('singer', [])
                    artist_name = '/'.join([s['name'] for s in singers]) if singers else '未知歌手'
                    
                    # 获取时长
                    duration_sec = int(song.get('interval', 0))
                    minutes, seconds = divmod(duration_sec, 60)
                    duration = f"{minutes}:{seconds:02d}"
                    
                    # 获取封面
                    album_mid = song.get('album', {}).get('mid', '')
                    pic_url = f"https://y.gtimg.cn/music/photo_new/T002R300x300M000{album_mid}.jpg"
                    
                    # 检查是否为VIP歌曲
                    is_vip = song.get('pay', {}).get('pay_play', 0) == 1
                    
                    # 估算文件大小
                    mp3_size = round(duration_sec * 320 / 8 / 1024, 1)  # MB
                    flac_size = round(duration_sec * 900 / 8 / 1024, 1)  # MB
                    
                    # 为VIP歌曲添加备注
                    size_mp3_text = f"{mp3_size} MB"
                    size_flac_text = f"{flac_size} MB"
                    if is_vip:
                        size_mp3_text += " (VIP)"
                        size_flac_text += " (VIP)"
                    
                    # 生成下载链接 - 不要在这里添加时间戳和guid，这些会在实际下载时生成
                    result_list.append({
                        'id': song_id,
                        'name': song_name,
                        'artist': artist_name,
                        'album': album_name,
                        'duration': duration,
                        'size_mp3': size_mp3_text,
                        'size_flac': size_flac_text,
                        'url_mp3': f"https://api.qq-download.com/QQ/{song_id}",  # 使用通用格式，实际下载时再处理
                        'url_flac': f"https://api.qq-download.com/QQ/{song_id}/flac",  # 使用通用格式，实际下载时再处理
                        'pic_url': pic_url,
                        'source': 'qq',
                        'is_vip': is_vip
                    })
                
                return result_list
                
            except Exception as e:
                print(f"QQ音乐请求出错: {str(e)}")
                return self._search_mock(keyword, limit)
            
        except Exception as e:
            print(f"QQ音乐搜索出错: {str(e)}")
            # 出错时返回备用mock数据
            return self._search_mock(keyword, limit)
    
    def _search_mock(self, keyword, limit=10):
        """返回模拟数据，作为API请求失败的备选方案"""
        mock_results = [
            {
                'id': '1', 
                'name': f'{keyword} - 夜曲', 
                'artist': '周杰伦', 
                'album': '十一月的萧邦', 
                'duration': '3:45',
                'size_mp3': '8.5 MB',
                'size_flac': '25.7 MB',
                'url_mp3': 'https://example.com/mp3/1.mp3',
                'url_flac': 'https://example.com/flac/1.flac',
                'pic_url': 'https://p1.music.126.net/AZEMp4gx0nzJJNFGVKFJNQ==/109951165671348533.jpg',
                'source': 'mock'
            },
            {
                'id': '2', 
                'name': f'{keyword} - 稻香', 
                'artist': '周杰伦', 
                'album': '魔杰座', 
                'duration': '3:42',
                'size_mp3': '8.3 MB',
                'size_flac': '24.9 MB',
                'url_mp3': 'https://example.com/mp3/2.mp3',
                'url_flac': 'https://example.com/flac/2.flac',
                'pic_url': 'https://p2.music.126.net/yFCpD3GrgmcInAbVvVaFUg==/109951165793869158.jpg',
                'source': 'mock'
            },
            {
                'id': '3', 
                'name': f'{keyword} - 七里香', 
                'artist': '周杰伦', 
                'album': '七里香', 
                'duration': '4:21',
                'size_mp3': '9.8 MB',
                'size_flac': '29.4 MB',
                'url_mp3': 'https://example.com/mp3/3.mp3',
                'url_flac': 'https://example.com/flac/3.flac',
                'pic_url': 'https://p2.music.126.net/2ePoXV3j-xWJHfpj-SkAcA==/109951166562834693.jpg',
                'source': 'mock'
            }
        ]
        
        return mock_results[:limit]
    
    def download_from_url(self, url, save_path, headers=None):
        """
        从URL下载文件，支持显示进度
        
        参数:
            url (str): 下载URL
            save_path (str): 保存路径
            headers (dict): 请求头
            
        返回:
            tuple: (成功标志, 消息)
        """
        try:
            if headers is None:
                headers = self.download_headers
            
            # 对于网易云和QQ音乐，我们首先获取真实下载链接（处理重定向）
            if 'music.163.com' in url or 'stream.qqmusic.qq.com' in url:
                try:
                    # 跟随重定向但不下载内容
                    with requests.get(url, headers=headers, stream=True, allow_redirects=True) as r:
                        r.raise_for_status()
                        # 如果没有内容或内容极小，说明链接可能无法访问（比如VIP限制）
                        if 'Content-Length' in r.headers and int(r.headers.get('Content-Length', 0)) < 1024:
                            # 尝试使用另一种方法获取音乐
                            if 'music.163.com' in url:
                                # 提取歌曲ID
                                song_id = None
                                if 'id=' in url:
                                    song_id = url.split('id=')[-1].split('.')[0]
                                elif '.mp3' in url:
                                    song_id = url.split('url?')[-1].split('.mp3')[0].strip()
                                
                                if song_id:
                                    # 尝试使用备用方法获取网易云音乐
                                    print(f"网易云音乐链接无法直接下载，尝试备用方法获取: {song_id}")
                                    # 默认使用第三方API尝试获取
                                    alternative_url = f"https://music.163.com/song/media/outer/url?id={song_id}.mp3"
                                    return self.download_from_alternative_source(alternative_url, save_path)
                            elif 'stream.qqmusic.qq.com' in url:
                                # 提取歌曲ID
                                song_id = None
                                if 'M500' in url:
                                    song_id = url.split('M500')[-1].split('.mp3')[0]
                                elif 'F000' in url:
                                    song_id = url.split('F000')[-1].split('.flac')[0]
                                
                                if song_id:
                                    # 尝试使用备用方法获取QQ音乐
                                    print(f"QQ音乐链接无法直接下载，尝试备用方法获取: {song_id}")
                                    # 生成新的GUID参数
                                    guid = ''.join(random.sample('1234567890', 10))
                                    alternative_url = f"https://dl.stream.qqmusic.qq.com/M500{song_id}.mp3?guid={guid}&vkey=&fromtag=1"
                                    return self.download_from_alternative_source(alternative_url, save_path)
                        
                        # 获取真实下载链接（可能经过重定向）
                        actual_url = r.url
                        # 使用最终的真实链接
                        url = actual_url
                except Exception as e:
                    print(f"获取真实下载链接出错: {str(e)}")
                    # 继续使用原始链接尝试下载
            
            # 下载文件
            with requests.get(url, headers=headers, stream=True) as response:
                total_size = int(response.headers.get('content-length', 0))
                
                if total_size == 0:
                    return False, "下载失败：无法获取文件大小"
                
                # 确保保存目录存在
                save_dir = os.path.dirname(save_path)
                if not os.path.exists(save_dir):
                    os.makedirs(save_dir)
                
                # 下载文件
                with open(save_path, 'wb') as file:
                    for chunk in response.iter_content(chunk_size=1024):
                        if chunk:
                            file.write(chunk)
                
                # 验证下载的文件是否为音频文件（检查文件大小）
                if os.path.getsize(save_path) < 10 * 1024:  # 小于10KB可能不是有效的音频文件
                    # 读取文件内容检查
                    with open(save_path, 'rb') as f:
                        content = f.read(1024).decode('utf-8', errors='ignore')
                        if '<html' in content or '<body' in content:
                            # 文件为HTML，说明下载失败，可能是VIP限制
                            os.remove(save_path)  # 删除无效文件
                            return False, "下载失败：该歌曲可能为VIP限制内容，无法直接下载"
                
                return True, f"下载完成: {os.path.basename(save_path)}"
            
        except Exception as e:
            if os.path.exists(save_path):
                try:
                    os.remove(save_path)  # 删除不完整的文件
                except:
                    pass
            return False, f"下载失败: {str(e)}"
    
    def download_from_alternative_source(self, url, save_path, max_attempts=3):
        """
        使用备用方法进行下载，特别适用于处理403错误
        
        参数:
            url (str): 下载链接
            save_path (str): 保存路径
            max_attempts (int): 最大尝试次数
        
        返回:
            bool: 下载是否成功
        """
        # 提取文件名
        filename = os.path.basename(save_path)
        temp_dir = os.path.dirname(save_path)
        
        # 确保目录存在
        os.makedirs(temp_dir, exist_ok=True)
        
        # 随机生成不同的User-Agent
        user_agents = [
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:90.0) Gecko/20100101 Firefox/90.0',
            'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.1.1 Safari/605.1.15',
            'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.164 Safari/537.36 Edg/91.0.864.71'
        ]
        
        # 添加不同的Referer
        referers = [
            'https://y.qq.com/',
            'https://y.qq.com/portal/player.html',
            'https://y.qq.com/n/ryqq/player',
            'https://y.qq.com/n/yqq/song/',
            'https://y.qq.com/n/yqq/album/'
        ]
        
        success = False
        attempt = 0
        
        while attempt < max_attempts and not success:
            try:
                # 随机选择UA和Referer
                user_agent = random.choice(user_agents)
                referer = random.choice(referers)
                
                # 创建自定义headers
                custom_headers = {
                    'User-Agent': user_agent,
                    'Referer': referer,
                    'Accept': '*/*',
                    'Accept-Encoding': 'gzip, deflate, br',
                    'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
                    'Range': 'bytes=0-',
                    'Connection': 'keep-alive',
                    'Origin': 'https://y.qq.com'
                }
                
                # 第一种方法：使用requests库分块下载
                print(f"方法1: 使用requests分块下载 (尝试 {attempt+1}/{max_attempts})")
                with requests.get(url, headers=custom_headers, stream=True, timeout=30) as r:
                    # 检查响应
                    if r.status_code == 403:
                        print("请求被拒绝(403)，尝试下一个方法")
                        raise Exception("403 Forbidden")
                    
                    r.raise_for_status()
                    
                    with open(save_path, 'wb') as f:
                        for chunk in r.iter_content(chunk_size=8192):
                            if chunk:
                                f.write(chunk)
                
                # 检查文件是否成功下载
                if os.path.exists(save_path) and os.path.getsize(save_path) > 0:
                    success = True
                    break
            
            except Exception as e:
                print(f"方法1下载失败: {str(e)}")
                
                try:
                    # 第二种方法：使用加密的UA和特殊的头信息
                    print(f"方法2: 使用高级请求方法 (尝试 {attempt+1}/{max_attempts})")
                    
                    # 添加特殊的头信息，模拟QQ音乐客户端
                    special_headers = {
                        'User-Agent': user_agent,
                        'Referer': referer,
                        'Cookie': f'qqmusic_guid={self._generate_random_guid()}',
                        'X-Forwarded-For': self._generate_random_ip(),
                        'MQQBrowser/26 Mozilla/5.0': 'MQQBrowser/26 Mozilla/5.0',
                        'Accept': '*/*',
                        'Accept-Encoding': 'gzip, deflate, br',
                        'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
                    }
                    
                    # 构建带有时间戳的URL，避免缓存
                    if '?' in url:
                        timestamped_url = f"{url}&_t={int(time.time())}"
                    else:
                        timestamped_url = f"{url}?_t={int(time.time())}"
                    
                    # 发送请求
                    with requests.get(timestamped_url, headers=special_headers, stream=True, timeout=30) as r:
                        r.raise_for_status()
                        
                        with open(save_path, 'wb') as f:
                            for chunk in r.iter_content(chunk_size=8192):
                                if chunk:
                                    f.write(chunk)
                    
                    # 检查文件是否成功下载
                    if os.path.exists(save_path) and os.path.getsize(save_path) > 10000:  # 确保文件大小至少为10KB
                        success = True
                        break
                
                except Exception as e:
                    print(f"方法2下载失败: {str(e)}")
                    
                    try:
                        # 第三种方法：使用curl命令
                        print(f"方法3: 使用系统命令下载 (尝试 {attempt+1}/{max_attempts})")
                        
                        if os.name == 'nt':  # Windows
                            command = f'curl -A "{user_agent}" -e "{referer}" -L --output "{save_path}" "{url}"'
                            os.system(command)
                        else:  # Linux/Mac
                            command = f'curl -A "{user_agent}" -e "{referer}" -L -o "{save_path}" "{url}"'
                            os.system(command)
                        
                        # 检查文件是否成功下载
                        if os.path.exists(save_path) and os.path.getsize(save_path) > 10000:
                            success = True
                            break
                    
                    except Exception as e:
                        print(f"方法3下载失败: {str(e)}")
            
            # 增加延迟，避免频繁请求
            time.sleep(random.uniform(1, 3))
            attempt += 1
        
        return success
    
    def _generate_random_guid(self):
        """生成随机GUID"""
        return ''.join(random.choices('0123456789abcdef', k=32))
    
    def _generate_random_ip(self):
        """生成随机IP地址"""
        return f"{random.randint(1, 255)}.{random.randint(0, 255)}.{random.randint(0, 255)}.{random.randint(1, 254)}"
    
    def get_song_url(self, song_id, format_type='mp3', source='netease'):
        """
        获取歌曲实际下载链接
        
        参数:
            song_id (str): 歌曲ID
            format_type (str): 'mp3'或'flac'
            source (str): 音乐源
            
        返回:
            str: 下载链接
        """
        if source == 'netease':
            # 添加时间戳防止缓存
            timestamp = int(time.time())
            # 网易云音乐直接返回外链
            return f"{self.netease_song_url}{song_id}.mp3?time={timestamp}"
        
        elif source == 'qq':
            # QQ音乐返回特殊格式URL，实际下载时再进行处理
            if format_type == 'mp3':
                return f"https://api.qq-download.com/QQ/{song_id}"
            else:
                return f"https://api.qq-download.com/QQ/{song_id}/flac"
        
        else:
            # 默认返回模拟链接
            return f"https://example.com/{format_type}/{song_id}.{format_type}"
    
    def download_from_youtube(self, video_url, output_dir, filename, format_type='mp3'):
        """
        从YouTube下载音乐
        
        参数:
            video_url (str): YouTube视频URL
            output_dir (str): 输出目录
            filename (str): 输出文件名（不包含扩展名）
            format_type (str): 输出格式，'mp3'或'flac'
            
        返回:
            tuple: (成功标志, 消息)
        """
        try:
            if not os.path.exists(output_dir):
                os.makedirs(output_dir)
                
            # 处理文件名，去除非法字符
            filename = re.sub(r'[\\/*?:"<>|]', '', filename)
            output_path = os.path.join(output_dir, f"{filename}.{format_type}")
            
            # 如果存在旧文件，先删除
            if os.path.exists(output_path):
                os.remove(output_path)
            
            ydl_opts = {
                'format': 'bestaudio/best',
                'outtmpl': os.path.join(output_dir, f"{filename}.%(ext)s"),
                'noplaylist': True,
                'quiet': False,
                'no_warnings': False,
            }
            
            if format_type == 'mp3':
                ydl_opts.update({
                    'postprocessors': [{
                        'key': 'FFmpegExtractAudio',
                        'preferredcodec': 'mp3',
                        'preferredquality': '320',
                    }],
                })
            elif format_type == 'flac':
                ydl_opts.update({
                    'postprocessors': [{
                        'key': 'FFmpegExtractAudio',
                        'preferredcodec': 'flac',
                        'preferredquality': '0',
                    }],
                })
            
            # 下载并转换
            with youtube_dl.YoutubeDL(ydl_opts) as ydl:
                info = ydl.extract_info(video_url, download=True)
                title = info.get('title', filename)
                
            return True, f"下载完成: {filename}.{format_type}"
            
        except Exception as e:
            return False, f"下载失败: {str(e)}"
    
    def download_with_metadata(self, url, save_path, metadata=None):
        """
        下载音乐并添加元数据（包括封面）
        
        参数:
            url (str): 下载URL
            save_path (str): 保存路径
            metadata (dict): 元数据，包含title, artist, album, cover_url等
            
        返回:
            tuple: (成功标志, 消息)
        """
        try:
            song_id = None
            is_vip = False
            source = 'unknown'
            
            # 尝试从URL中提取歌曲ID和来源
            if 'music.163.com' in url:
                source = 'netease'
                if 'id=' in url:
                    song_id = url.split('id=')[-1].split('.')[0]
                elif '.mp3' in url:
                    song_id = url.split('url?')[-1].split('.mp3')[0].strip()
                
                # 检查是否为VIP歌曲
                if metadata and 'is_vip' in metadata:
                    is_vip = metadata['is_vip']
            elif 'api.qq-download.com' in url:
                source = 'qq'
                song_id = url.split('/')[-1]
                if song_id == 'flac':
                    song_id = url.split('/')[-2]
                
                # 检查是否为VIP歌曲
                if metadata and 'is_vip' in metadata:
                    is_vip = metadata['is_vip']
                
                # 处理QQ音乐下载
                if song_id:
                    # 对于QQ音乐，使用专业下载方法，不管是否为VIP
                    success, message = self.download_qq_music(song_id, save_path, metadata)
                    return success, message
            elif 'qqmusic.qq.com' in url or 'stream.qqmusic.qq.com' in url:
                source = 'qq'
                if 'M500' in url:
                    song_id = url.split('M500')[-1].split('.mp3')[0]
                elif 'F000' in url:
                    song_id = url.split('F000')[-1].split('.flac')[0]
                
                # 检查是否为VIP歌曲
                if metadata and 'is_vip' in metadata:
                    is_vip = metadata['is_vip']
            
            # 对于VIP歌曲，尝试使用专业下载方法
            if is_vip and song_id:
                # 尝试使用专业下载器下载VIP歌曲
                if source == 'netease':
                    success, message = self.download_netease_vip(song_id, save_path, metadata)
                    if success:
                        return True, message
                elif source == 'qq':
                    success, message = self.download_qq_vip(song_id, save_path, metadata)
                    if success:
                        return True, message
            
            # 首先下载音乐文件
            success, message = self.download_from_url(url, save_path)
            if not success:
                # 如果常规下载失败，尝试备用方法
                if song_id and source == 'netease':
                    return self.download_netease_vip(song_id, save_path, metadata)
                elif song_id and source == 'qq':
                    return self.download_qq_vip(song_id, save_path, metadata)
                else:
                    return False, message
            
            # 如果没有元数据，直接返回
            if not metadata:
                return True, message
            
            # 根据文件格式添加元数据
            file_ext = os.path.splitext(save_path)[1].lower()
            
            # 下载封面图
            cover_data = None
            if 'cover_url' in metadata and metadata['cover_url']:
                try:
                    # 为封面请求添加请求头
                    cover_headers = {
                        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36',
                        'Referer': 'https://music.163.com/' if source == 'netease' else 'https://y.qq.com/'
                    }
                    cover_response = requests.get(metadata['cover_url'], headers=cover_headers)
                    if cover_response.status_code == 200:
                        cover_data = cover_response.content
                except Exception as e:
                    print(f"下载封面图出错: {str(e)}")
            
            if file_ext == '.mp3':
                self._add_mp3_metadata(save_path, metadata, cover_data)
            elif file_ext == '.flac':
                self._add_flac_metadata(save_path, metadata, cover_data)
            
            return True, f"下载完成并添加元数据: {os.path.basename(save_path)}"
            
        except Exception as e:
            return False, f"添加元数据失败: {str(e)}"
    
    def download_netease_vip(self, song_id, save_path, metadata=None):
        """使用专业方法下载网易云VIP歌曲"""
        try:
            # 尝试使用多种专业方法下载VIP歌曲
            
            # 方法1: 使用第三方API下载（模拟移动端请求）
            try:
                # 定义API端点列表
                api_endpoints = [
                    f"https://music.163.com/song/media/outer/url?id={song_id}.mp3",  # 标准外链
                    f"https://music.163.com/song/enhance/player/url?id={song_id}&ids=%5B{song_id}%5D&br=999000",  # 增强API
                    f"http://music.163.com/song/media/outer/url?id={song_id}.mp3",  # 标准外链(HTTP)
                    f"https://api.injahow.cn/music/?id={song_id}&type=song&br=999000"  # 第三方增强API
                ]
                
                # 添加移动端请求头
                mobile_headers = {
                    'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1',
                    'Referer': 'https://music.163.com/',
                    'Accept': '*/*',
                    'Accept-Encoding': 'gzip, deflate, br',
                    'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8'
                }
                
                # 尝试每个API端点
                for endpoint in api_endpoints:
                    try:
                        response = requests.get(endpoint, headers=mobile_headers, stream=True, timeout=10)
                        if response.status_code == 200:
                            content_type = response.headers.get('Content-Type', '')
                            if 'audio' in content_type or 'application/octet-stream' in content_type:
                                # 下载文件
                                with open(save_path, 'wb') as file:
                                    for chunk in response.iter_content(chunk_size=1024):
                                        if chunk:
                                            file.write(chunk)
                                
                                # 验证文件大小
                                if os.path.getsize(save_path) > 1024 * 100:  # 大于100KB认为有效
                                    # 如果有元数据，添加元数据
                                    if metadata:
                                        # 下载封面
                                        cover_data = None
                                        if 'cover_url' in metadata and metadata['cover_url']:
                                            try:
                                                cover_headers = {
                                                    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36',
                                                    'Referer': 'https://music.163.com/'
                                                }
                                                cover_response = requests.get(metadata['cover_url'], headers=cover_headers)
                                                if cover_response.status_code == 200:
                                                    cover_data = cover_response.content
                                            except:
                                                pass
                                        
                                        # 添加元数据
                                        file_ext = os.path.splitext(save_path)[1].lower()
                                        if file_ext == '.mp3':
                                            self._add_mp3_metadata(save_path, metadata, cover_data)
                                        elif file_ext == '.flac':
                                            self._add_flac_metadata(save_path, metadata, cover_data)
                                    
                                    return True, f"成功下载VIP歌曲: {os.path.basename(save_path)}"
                    except Exception as e:
                        print(f"尝试下载VIP歌曲失败 - {endpoint}: {str(e)}")
                        continue
            except Exception as e:
                print(f"方法1下载失败: {str(e)}")
            
            # 方法2: 尝试使用第三方直链服务
            try:
                third_party_apis = [
                    f"https://autumnfish.cn/song/url?id={song_id}",
                    f"https://api.paugram.com/netease/?id={song_id}",
                    f"https://api.xingzhige.com/API/NetEaseVIP/?id={song_id}",
                    f"https://api.mlwei.com/music/api/wy/?type=url&id={song_id}&quality=128"
                ]
                
                headers = {
                    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36'
                }
                
                for api in third_party_apis:
                    try:
                        response = requests.get(api, headers=headers)
                        if response.status_code == 200:
                            try:
                                result = response.json()
                                url = None
                                
                                # 不同API返回格式不同，尝试解析
                                if 'data' in result and isinstance(result['data'], list) and len(result['data']) > 0:
                                    url = result['data'][0].get('url')
                                elif 'data' in result and isinstance(result['data'], dict):
                                    url = result['data'].get('url')
                                elif 'url' in result:
                                    url = result['url']
                                elif 'link' in result:
                                    url = result['link']
                                
                                if url and url.strip() != '' and 'http' in url:
                                    # 使用获取到的URL下载
                                    dl_response = requests.get(url, stream=True, timeout=10)
                                    if dl_response.status_code == 200:
                                        content_type = dl_response.headers.get('Content-Type', '')
                                        if 'audio' in content_type or 'application/octet-stream' in content_type:
                                            with open(save_path, 'wb') as file:
                                                for chunk in dl_response.iter_content(chunk_size=1024):
                                                    if chunk:
                                                        file.write(chunk)
                                            
                                            # 验证文件大小
                                            if os.path.getsize(save_path) > 1024 * 100:  # 大于100KB认为有效
                                                if metadata:
                                                    # 添加元数据
                                                    file_ext = os.path.splitext(save_path)[1].lower()
                                                    if file_ext == '.mp3':
                                                        self._add_mp3_metadata(save_path, metadata, None)
                                                    elif file_ext == '.flac':
                                                        self._add_flac_metadata(save_path, metadata, None)
                                                
                                                return True, f"成功下载VIP歌曲(第三方API): {os.path.basename(save_path)}"
                            except:
                                continue
                    except:
                        continue
            except Exception as e:
                print(f"方法2下载失败: {str(e)}")
            
            # 所有方法都失败，返回错误
            return False, "无法下载VIP歌曲，所有方法均已尝试"
            
        except Exception as e:
            return False, f"下载VIP歌曲失败: {str(e)}"
    
    def download_qq_vip(self, song_id, save_path, metadata=None):
        """使用专业方法下载QQ音乐VIP歌曲"""
        try:
            # 方法1: 使用第三方API下载
            try:
                # 第三方API端点列表
                api_endpoints = [
                    f"https://api.mlwei.com/music/api/qq/?type=url&id={song_id}&quality=128",
                    f"https://api.paugram.com/qqmusic/?id={song_id}",
                    f"https://api.xingzhige.com/API/QQmusicVIP/?mid={song_id}"
                ]
                
                headers = {
                    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36',
                    'Referer': 'https://y.qq.com/'
                }
                
                for api in api_endpoints:
                    try:
                        response = requests.get(api, headers=headers, timeout=10)
                        if response.status_code == 200:
                            try:
                                result = response.json()
                                url = None
                                
                                # 尝试解析不同格式的API返回
                                if 'url' in result:
                                    url = result['url']
                                elif 'data' in result and isinstance(result['data'], dict) and 'url' in result['data']:
                                    url = result['data']['url']
                                elif 'link' in result:
                                    url = result['link']
                                
                                if url and url.strip() != '' and 'http' in url:
                                    # 使用获取到的URL下载
                                    dl_response = requests.get(url, stream=True, timeout=15)
                                    if dl_response.status_code == 200:
                                        with open(save_path, 'wb') as file:
                                            for chunk in dl_response.iter_content(chunk_size=1024):
                                                if chunk:
                                                    file.write(chunk)
                                        
                                        # 验证文件大小
                                        if os.path.getsize(save_path) > 1024 * 100:  # 大于100KB认为有效
                                            if metadata:
                                                # 添加元数据
                                                file_ext = os.path.splitext(save_path)[1].lower()
                                                if file_ext == '.mp3':
                                                    self._add_mp3_metadata(save_path, metadata, None)
                                                elif file_ext == '.flac':
                                                    self._add_flac_metadata(save_path, metadata, None)
                                            
                                            return True, f"成功下载QQ音乐VIP歌曲: {os.path.basename(save_path)}"
                            except:
                                continue
                    except:
                        continue
            except Exception as e:
                print(f"QQ音乐VIP下载方法1失败: {str(e)}")
            
            # 方法2: 尝试直接使用特殊格式URL
            try:
                # 生成一些随机参数
                guid = ''.join(random.sample('1234567890', 10))
                timestamp = int(time.time() * 1000)
                
                # 尝试不同的URL模式
                url_templates = [
                    f"https://isure.stream.qqmusic.qq.com/M500{song_id}.mp3?guid={guid}&vkey=&fromtag=30",
                    f"https://aqqmusic.tc.qq.com/amobile.music.tc.qq.com/M500{song_id}.mp3?guid={guid}",
                    f"https://dl.stream.qqmusic.qq.com/M500{song_id}.mp3?guid={guid}&vkey=&fromtag=30&cr=3"
                ]
                
                for url in url_templates:
                    try:
                        # 构建请求头，模拟移动端
                        mobile_headers = {
                            'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1',
                            'Referer': 'https://y.qq.com/',
                            'Accept': '*/*',
                            'Accept-Encoding': 'gzip, deflate, br',
                            'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8'
                        }
                        
                        response = requests.get(url, headers=mobile_headers, stream=True, timeout=10)
                        if response.status_code == 200:
                            content_type = response.headers.get('Content-Type', '')
                            if 'audio' in content_type or 'application/octet-stream' in content_type:
                                with open(save_path, 'wb') as file:
                                    for chunk in response.iter_content(chunk_size=1024):
                                        if chunk:
                                            file.write(chunk)
                                
                                # 验证文件大小
                                if os.path.getsize(save_path) > 1024 * 100:  # 大于100KB认为有效
                                    if metadata:
                                        # 添加元数据
                                        file_ext = os.path.splitext(save_path)[1].lower()
                                        if file_ext == '.mp3':
                                            self._add_mp3_metadata(save_path, metadata, None)
                                        elif file_ext == '.flac':
                                            self._add_flac_metadata(save_path, metadata, None)
                                    
                                    return True, f"成功下载QQ音乐VIP歌曲(直链): {os.path.basename(save_path)}"
                    except:
                        continue
            except Exception as e:
                print(f"QQ音乐VIP下载方法2失败: {str(e)}")
            
            # 所有方法都失败，返回错误
            return False, "无法下载QQ音乐VIP歌曲，所有方法均已尝试"
            
        except Exception as e:
            return False, f"下载QQ音乐VIP歌曲失败: {str(e)}"
    
    def _add_mp3_metadata(self, file_path, metadata, cover_data=None):
        """为MP3文件添加元数据"""
        try:
            # 尝试加载现有ID3标签，如果不存在则创建新的
            try:
                audio = ID3(file_path)
            except:
                audio = ID3()
            
            # 添加标题
            if 'title' in metadata and metadata['title']:
                audio.add(TIT2(encoding=3, text=metadata['title']))
                
            # 添加艺术家
            if 'artist' in metadata and metadata['artist']:
                audio.add(TPE1(encoding=3, text=metadata['artist']))
                
            # 添加专辑
            if 'album' in metadata and metadata['album']:
                audio.add(TALB(encoding=3, text=metadata['album']))
                
            # 添加封面
            if cover_data:
                audio.add(APIC(
                    encoding=3,
                    mime='image/jpeg',
                    type=3,  # 3 是封面图片
                    desc='Cover',
                    data=cover_data
                ))
                
            # 保存到文件
            audio.save(file_path)
            
        except Exception as e:
            print(f"添加MP3元数据出错: {str(e)}")
    
    def _add_flac_metadata(self, file_path, metadata, cover_data=None):
        """为FLAC文件添加元数据"""
        try:
            audio = FLAC(file_path)
            
            # 添加标题
            if 'title' in metadata and metadata['title']:
                audio['TITLE'] = metadata['title']
                
            # 添加艺术家
            if 'artist' in metadata and metadata['artist']:
                audio['ARTIST'] = metadata['artist']
                
            # 添加专辑
            if 'album' in metadata and metadata['album']:
                audio['ALBUM'] = metadata['album']
                
            # 添加封面
            if cover_data:
                picture = Picture()
                picture.data = cover_data
                picture.type = 3  # 3 是封面图片
                picture.mime = 'image/jpeg'
                picture.desc = 'Cover'
                
                image = picture.write()
                audio.add_picture(image)
                
            # 保存到文件
            audio.save()
            
        except Exception as e:
            print(f"添加FLAC元数据出错: {str(e)}")
    
    def download_qq_music(self, song_id, save_path, metadata=None):
        """使用多种方法下载QQ音乐（通用方法，适用于VIP和非VIP）"""
        try:
            # 方法1: 使用第三方API服务
            api_endpoints = [
                f"https://api.vvhan.com/api/music?id={song_id}&type=qq",
                f"https://api.xingzhige.com/API/QQmusicVIP/?mid={song_id}",
                f"https://api.xiaobaibk.com/api/qqyy/api.php?url={song_id}"
            ]
            
            # 模拟各种请求头
            headers_list = [
                {
                    "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 14_6 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Mobile/15E148 Safari/604.1",
                    "Referer": "https://y.qq.com/",
                    "Accept": "*/*"
                },
                {
                    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36",
                    "Referer": "https://y.qq.com/portal/player.html",
                    "Accept": "*/*"
                },
                {
                    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36",
                    "Referer": "https://y.qq.com/",
                    "Accept": "*/*"
                }
            ]
            
            # 尝试每一个API端点
            for api in api_endpoints:
                for headers in headers_list:
                    try:
                        response = requests.get(api, headers=headers, timeout=15)
                        if response.status_code == 200:
                            try:
                                # 尝试解析JSON响应
                                data = response.json()
                                
                                # 尝试从不同格式的API响应中提取URL
                                download_url = None
                                
                                if 'link' in data:
                                    download_url = data.get('link')
                                elif 'url' in data:
                                    download_url = data.get('url')
                                elif 'data' in data and isinstance(data['data'], dict) and 'url' in data['data']:
                                    download_url = data['data'].get('url')
                                
                                if download_url and 'http' in download_url:
                                    # 下载歌曲
                                    dl_response = requests.get(download_url, headers=headers, stream=True, timeout=30)
                                    if dl_response.status_code == 200:
                                        with open(save_path, 'wb') as f:
                                            for chunk in dl_response.iter_content(chunk_size=1024):
                                                if chunk:
                                                    f.write(chunk)
                                        
                                        # 检查文件是否有效
                                        if os.path.getsize(save_path) > 1024 * 100:
                                            # 添加元数据
                                            if metadata:
                                                # 下载封面图
                                                cover_data = None
                                                if 'cover_url' in metadata and metadata['cover_url']:
                                                    try:
                                                        cover_headers = {
                                                            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36',
                                                            'Referer': 'https://y.qq.com/'
                                                        }
                                                        cover_response = requests.get(metadata['cover_url'], headers=cover_headers)
                                                        if cover_response.status_code == 200:
                                                            cover_data = cover_response.content
                                                    except:
                                                        pass
                                                
                                                # 添加元数据
                                                file_ext = os.path.splitext(save_path)[1].lower()
                                                if file_ext == '.mp3':
                                                    self._add_mp3_metadata(save_path, metadata, cover_data)
                                                elif file_ext == '.flac':
                                                    self._add_flac_metadata(save_path, metadata, cover_data)
                                            
                                            return True, f"下载成功: {os.path.basename(save_path)}"
                            except:
                                # 可能不是JSON响应，尝试其他方法
                                pass
                    except:
                        continue
            
            # 方法2: 使用直接的QQ音乐URL
            # 生成新的随机参数
            guid = ''.join(random.sample('1234567890', 10))
            timestamp = int(time.time() * 1000)
            
            # 尝试不同的下载URL模板
            url_templates = [
                # 尝试普通模板
                f"https://dl.stream.qqmusic.qq.com/M500{song_id}.mp3?guid={guid}&vkey=&fromtag=1&uin=0",
                f"http://dl.stream.qqmusic.qq.com/M500{song_id}.mp3?guid={guid}&vkey=&fromtag=1&uin=0",
                # 尝试备用CDN
                f"https://isure.stream.qqmusic.qq.com/M500{song_id}.mp3?guid={guid}",
                f"https://aqqmusic.tc.qq.com/amobile.music.tc.qq.com/M500{song_id}.mp3?guid={guid}",
                # 尝试非官方格式
                f"https://wsaudio.qq.com/fcgi/fcg_music_get_song?song_id={song_id}&filetype=1&uin=0&guid={guid}",
                # 尝试媒体平台格式
                f"https://c6.y.qq.com/rsc/fcgi-bin/fcg_pyq_play.fcg?songid={song_id}&songmid={song_id}"
            ]
            
            for url in url_templates:
                for headers in headers_list:
                    try:
                        response = requests.get(url, headers=headers, stream=True, timeout=15)
                        if response.status_code == 200:
                            # 检查内容类型
                            content_type = response.headers.get('Content-Type', '')
                            if 'audio' in content_type or 'application/octet-stream' in content_type:
                                # 下载歌曲
                                with open(save_path, 'wb') as f:
                                    for chunk in response.iter_content(chunk_size=1024):
                                        if chunk:
                                            f.write(chunk)
                                
                                # 检查文件是否有效
                                if os.path.getsize(save_path) > 1024 * 100:
                                    # 添加元数据
                                    if metadata:
                                        file_ext = os.path.splitext(save_path)[1].lower()
                                        if file_ext == '.mp3':
                                            self._add_mp3_metadata(save_path, metadata, None)
                                        elif file_ext == '.flac':
                                            self._add_flac_metadata(save_path, metadata, None)
                                    
                                    return True, f"下载成功: {os.path.basename(save_path)}"
                    except:
                        continue
            
            # 如果所有方法都失败，使用备用方法
            return self.download_qq_vip(song_id, save_path, metadata)
            
        except Exception as e:
            print(f"QQ音乐下载出错: {str(e)}")
            return False, f"下载失败: {str(e)}" 